
<!DOCTYPE html>
<meta charset="utf-8">
<style>
    circle,
    path {
        cursor: pointer;
    }
    
    circle {
        fill: none;
        pointer-events: all;
    }
</style>

<body>
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script>
        var margin = {
                top: 350,
                right: 480,
                bottom: 350,
                left: 480
            },
            radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 10;

        var hue = d3.scale.category10();

        var luminance = d3.scale.sqrt()
            .domain([0, 1e6])
            .clamp(true)
            .range([90, 20]);

        var svg = d3.select("body").append("svg")
            .attr("width", margin.left + margin.right)
            .attr("height", margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

        var partition = d3.layout.partition()
            .sort(function(a, b) {
                return d3.ascending(a.name, b.name);
            })
            .size([2 * Math.PI, radius]);

        var arc = d3.svg.arc()
            .startAngle(function(d) {
                return d.x;
            })
            .endAngle(function(d) {
                return d.x + d.dx;
            })
            .padAngle(.01)
            .padRadius(radius / 3)
            .innerRadius(function(d) {
                return radius / 3 * d.depth;
            })
            .outerRadius(function(d) {
                return radius / 3 * (d.depth + 1) - 1;
            });

        d3.json("flare.json", function(error, root) {

            // Compute the initial layout on the entire tree to sum sizes.
            // Also compute the full name and fill color for each node,
            // and stash the children so they can be restored as we descend.
            partition
                .value(function(d) {
                    return d.size;
                })
                .nodes(root)
                .forEach(function(d) {
                    d._children = d.children;
                    d.sum = d.value;
                    d.key = key(d);
                    d.fill = fill(d);
                });

            // Now redefine the value function to use the previously-computed sum.
            partition
                .children(function(d, depth) {
                    return depth < 2 ? d._children : null;
                })
                .value(function(d) {
                    return d.sum;
                });

            var center = svg.append("circle")
                .attr("r", radius / 3)
                .on("click", zoomOut);

            center.append("title")
                .text("zoom out");

            var path = svg.selectAll("path")
                .data(partition.nodes(root).slice(1))
                .enter().append("path")
                .attr("d", arc)
                .style("fill", function(d) {
                    return d.fill;
                })
                .each(function(d) {
                    this._current = updateArc(d);
                })
                .on("click", zoomIn);

            function zoomIn(p) {
                if (p.depth > 1) p = p.parent;
                if (!p.children) return;
                zoom(p, p);
            }

            function zoomOut(p) {
                if (!p.parent) return;
                zoom(p.parent, p);
            }

            // Zoom to the specified new root.
            function zoom(root, p) {
                if (document.documentElement.__transition__) return;

                // Rescale outside angles to match the new layout.
                var enterArc,
                    exitArc,
                    outsideAngle = d3.scale.linear().domain([0, 2 * Math.PI]);

                function insideArc(d) {
                    return p.key > d.key ?
                        {
                            depth: d.depth - 1,
                            x: 0,
                            dx: 0
                        } : p.key < d.key ?
                        {
                            depth: d.depth - 1,
                            x: 2 * Math.PI,
                            dx: 0
                        } :
                        {
                            depth: 0,
                            x: 0,
                            dx: 2 * Math.PI
                        };
                }

                function outsideArc(d) {
                    return {
                        depth: d.depth + 1,
                        x: outsideAngle(d.x),
                        dx: outsideAngle(d.x + d.dx) - outsideAngle(d.x)
                    };
                }

                center.datum(root);

                // When zooming in, arcs enter from the outside and exit to the inside.
                // Entering outside arcs start from the old layout.
                if (root === p) enterArc = outsideArc, exitArc = insideArc, outsideAngle.range([p.x, p.x + p.dx]);

                path = path.data(partition.nodes(root).slice(1), function(d) {
                    return d.key;
                });

                // When zooming out, arcs enter from the inside and exit to the outside.
                // Exiting outside arcs transition to the new layout.
                if (root !== p) enterArc = insideArc, exitArc = outsideArc, outsideAngle.range([p.x, p.x + p.dx]);

                d3.transition().duration(d3.event.altKey ? 7500 : 750).each(function() {
                    path.exit().transition()
                        .style("fill-opacity", function(d) {
                            return d.depth === 1 + (root === p) ? 1 : 0;
                        })
                        .attrTween("d", function(d) {
                            return arcTween.call(this, exitArc(d));
                        })
                        .remove();

                    path.enter().append("path")
                        .style("fill-opacity", function(d) {
                            return d.depth === 2 - (root === p) ? 1 : 0;
                        })
                        .style("fill", function(d) {
                            return d.fill;
                        })
                        .on("click", zoomIn)
                        .each(function(d) {
                            this._current = enterArc(d);
                        });

                    path.transition()
                        .style("fill-opacity", 1)
                        .attrTween("d", function(d) {
                            return arcTween.call(this, updateArc(d));
                        });
                });
            }
        });

        function key(d) {
            var k = [],
                p = d;
            while (p.depth) k.push(p.name), p = p.parent;
            return k.reverse().join(".");
        }

        function fill(d) {
            var p = d;
            while (p.depth > 1) p = p.parent;
            var c = d3.lab(hue(p.name));
            c.l = luminance(d.sum);
            return c;
        }

        function arcTween(b) {
            var i = d3.interpolate(this._current, b);
            this._current = i(0);
            return function(t) {
                return arc(i(t));
            };
        }

        function updateArc(d) {
            return {
                depth: d.depth,
                x: d.x,
                dx: d.dx
            };
        }

        d3.select(self.frameElement).style("height", margin.top + margin.bottom + "px");
    </script>